import Java from "frida-java-bridge";
import { Logger } from "./log.js";
import { getSelfPackageName } from "./utils.js";
import { IOHelper } from "./io.js";

const TAG = "Dump";

/**
 * 从某个地址 dump 内存文件
 */
export function dumpMemoryFile(
  base: NativePointer,
  size: number,
  path: string,
) {
  var fd = new File(path, "wb");
  if (fd && fd != null) {
    var dex_buffer = base.readByteArray(size);
    if (dex_buffer != null) {
      fd.write(dex_buffer);
      fd.flush();
      fd.close();
      Logger.i(TAG, path);
    }
  }
}

function dumpDexFile(base: NativePointer, size: number, path: string) {
  // NOTE: 检查Magic
  var magic = base.readCString();
  if (magic == null) {
    return;
  }
  if (magic.indexOf("dex") != 0) {
    return;
  }

  dumpMemoryFile(base, size, path);
}

function getPackageName(packageName: any) {
  if (packageName != "" || packageName != null || packageName != undefined) {
    Logger.i(TAG, "Try to get package name...");
    return packageName;
  }

  var package_name = getSelfPackageName();
  // NOTE： 找不到包名
  if (package_name == "-1") {
    return null;
  }
}

function genarateDumpDir(packageName: any) {
  var package_name = getPackageName(packageName);
  if (package_name == null) {
    return null;
  }

  var dex_dir_path = "/data/data/" + package_name + "/files/dump_dex";
  IOHelper.mkdir(dex_dir_path);
  return dex_dir_path;
}

export const DumpDex = {
  /**
   * 通过 hook defindClass，把 dex 转储到本地。
   *
   * 1. 获取libart.so的地址，枚举符号找到DefineClass的函数地址
   * 2. 使用Interceptor内联hook DefineClass函数，获取 dexFile 对象的base和size
   * 3. 在包名的files目录下创建一个dump_dex_包名的文件夹，从 dexfile 的base读取size个字节写入文件
   */
  defineClass(packageName: any) {
    const dumpDir = genarateDumpDir(packageName);
    if (dumpDir == null) {
      Logger.e(TAG, "Can't get package name and generate dump dir");
      return;
    }

    var libart = Process.findModuleByName("libart.so");
    if (libart == null) {
      return;
    }

    var defineClassAddr = null;
    //枚举函数符号，查找defineclass的函数签名
    var symbols = libart.enumerateSymbols();
    for (var index = 0; index < symbols.length; index++) {
      var symbol = symbols[index];
      var symbol_name = symbol.name;
      /*
      这个DefineClass的函数签名是android 9的
      _ZN3art11ClassLinker11DefineClassEPNS_6ThreadEPKcmNS_6HandleINS_6mirror11ClassLoaderEEERKNS_7DexFileERKNS9_8ClassDefE

      android 10的DefineClass函数签名
      _ZN3art11ClassLinker11DefineClassEPNS_6ThreadEPKcmNS_6HandleINS_6mirror11ClassLoaderEEERKNS_7DexFileERKNS_3dex8ClassDefE
        mirror::Class* ClassLinker::DefineClass(Thread* self,
                                        const char* descriptor,
                                        size_t hash,
                                        Handle<mirror::ClassLoader> class_loader,
                                        const DexFile& dex_file,
                                        const DexFile::ClassDef& dex_class_def) 
        可以看到DefineClass的参数有一个dexfile对象                                                      
        */
      if (
        symbol_name.indexOf("ClassLinker") >= 0 &&
        symbol_name.indexOf("DefineClass") >= 0 &&
        symbol_name.indexOf("Thread") >= 0 &&
        symbol_name.indexOf("DexFile") >= 0
      ) {
        Logger.i(TAG, symbol_name + " - " + symbol.address);
        defineClassAddr = symbol.address;
      }
    }

    if (defineClassAddr == null) {
      console.log("没有找到defineClass函数符号信息，退出!");
      return;
    }

    var dex_infos = new Set();
    var dex_count = 1;

    Interceptor.attach(defineClassAddr, {
      onEnter: function (args) {
        var dex_file = args[5];
        var base = dex_file.add(Process.pointerSize).readPointer();
        var size = dex_file
          .add(Process.pointerSize + Process.pointerSize)
          .readUInt();

        // NOTE: 一样大小的Dex
        if (dex_infos.has(size)) {
          return;
        }
        dex_infos.add(size);

        // NOTE: 同一个地址
        if (dex_infos.has(base)) {
          return;
        }
        dex_infos.add(base);

        var dex_path = dumpDir + "/class_" + size + ".dex";
        dumpDexFile(base, size, dex_path);
      },
      onLeave: function (retval) {},
    });
  },

  /**
   * Android 9 以上
   * Hook openCommon函数，获取dexfile的base和size.
   */
  openCommon(packageName: any) {
    const dumpDir = genarateDumpDir(packageName);
    if (dumpDir == null) {
      Logger.e(TAG, "Can't get package name and generate dump dir");
      return;
    }

    var libdexfile = Process.findModuleByName("libdexfile.so");
    if (libdexfile == null) {
      return;
    }

    var funcAddr = null;
    var symbols = libdexfile.enumerateSymbols();
    for (var index = 0; index < symbols.length; index++) {
      var symbol = symbols[index];
      var symbol_name = symbol.name;
      // _ZN3art13DexFileLoader10OpenCommonEPKhjS2_jRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_NS3_10unique_ptrINS_16DexFileContainerENS3_14default_deleteISH_EEEEPNS0_12VerifyResultE
      if (
        symbol_name.indexOf("DexFileLoader") >= 0 &&
        symbol_name.indexOf("OpenCommon") >= 0 &&
        symbol_name.indexOf("DexFileContainer") >= 0 &&
        symbol_name.indexOf("VerifyResult") >= 0
      ) {
        Logger.i(TAG, symbol_name + " - " + symbol.address);
        funcAddr = symbol.address;
      }
    }

    if (funcAddr == null) {
      return;
    }

    var dex_infos = new Set();
    Interceptor.attach(funcAddr, {
      onEnter: function (args) {
        //dex在内存中的起始位置
        var base = args[1];
        var size = args[2];

        // TODO: 等待测试
        var dex_size = size.toInt32();
        // var dex_size2 = parseInt(size.toString(), 16);

        // NOTE: 一样大小的Dex
        if (dex_infos.has(dex_size)) {
          return;
        }
        dex_infos.add(dex_size);

        // NOTE: 同一个地址
        if (dex_infos.has(base)) {
          return;
        }
        dex_infos.add(base);

        var dex_path = dumpDir + "/class_" + dex_size + ".dex";
        dumpDexFile(base, dex_size, dex_path);
      },
      onLeave: function (retval) {},
    });
  },
};

export const DumpSo = {};
